In [1]:
#Here I am importing the features needed,Pandas is for data  loading the data, cleaning it etc
#I have imported numpy as this is used  for the machine learning model outputs and manipulating them
#I have imported Shap in order to use it at the end of this code for the explainability element of the project
#to help give further insight into what the outputs will be showing. 
import pandas as pd
import numpy as np
import shap
from lime.lime_tabular import LimeTabularExplainer

#This element will allow me to split the data into train/test, this will then be used to teach the models
from sklearn.model_selection import train_test_split

#This element is used within the machine learning models to prevent any one feature from dominating which is
#important as the majority of my dataset is of staff that did not leave which means there is some heavy 
#weighting there that needs to be standardised
from sklearn.preprocessing import StandardScaler

#These are the three Machine learning models I will be comparing to see which has the best performance, all
#of these are classification models that here I will be looking at leavers from the agency in the first instance
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

#These are my evaluation metrics that will be used to assess the success of the above models, they are key
#because when the original outputs came through they showed 99% accuracy which wasn't correct and actually 
#indicated that there was an issue with the data clearning
from sklearn.metrics import (
    accuracy_score,
    classification_report,
    confusion_matrix,
    roc_auc_score
)

#These are imported to allow visualisations to be created with the results
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import RepeatedStratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from scipy.stats import ttest_rel
from statsmodels.stats.anova import AnovaRM

# This is where I have loaded the data, the sheet name here is showing that I used that data that was correct
#up to the 30th April. The filenames shown are not relevant to whether I processed the data or not and really
#they shouldn't be taken into consideration into anything. 
capability = pd.read_excel(
    r"C:\Users\GelsthN\OneDrive - DVLA\Desktop\Level 6 Project\unprocessedData\capabilityPlan.xlsx",
    sheet_name="30.04.26"
)

training = pd.read_excel(
    r"C:\Users\GelsthN\OneDrive - DVLA\Desktop\Level 6 Project\unprocessedData\projectData.xlsx"
)

# Here I am having to convert the Staff number in the data sources into string, this is because originally
# when the first iteration of the code was run the results didn't work, it was because the staff numbers
#across both of the data sources were not the same, and so this cleaning needed to happen in order to get
#the machine learning models to work. 
capability["Staff Number"] = capability["Staff Number"].astype(str)
training["Staff Number"] = training["Staff Number"].astype(str)
capability["Staff Number"] = (
    capability["Staff Number"]
    .astype(str)
    .str.strip()
    .str.replace(".0", "", regex=False)
)

training["Staff Number"] = (
    training["Staff Number"]
    .astype(str)
    .str.strip()
    .str.replace(".0", "", regex=False)
)

# Again here I am making sure that the Cost and duration are numeric values so taht the sum that is needed
#to later on be calculated can be done. Again this was to remedy errors in the output where the sums for
#these features were not working at all in the first couple of iterations.  This is specifically forcing 
#everything to be a number, and if it is not able to it will make into NAN, or putting 0 in the blanks,
#This is all to ensure that the sums can be calculated. 
training["Cost"] = pd.to_numeric(training["Cost"], errors="coerce").fillna(0)
training["Duration (days) this FY"] = pd.to_numeric(
    training["Duration (days) this FY"], errors="coerce"
).fillna(0)

# In this section I am taking the training information which is stored as multiple lines per person, and
#each course is a row by itself, and grouping them all together by staff number. This will allow the machine
#learning algorithms to work as I intend. I am creating four features here 2 are sums, one counts how many
# unique courses there are and the other is a count. The reset_index at the end is what put this information
#back into the data frame
training_features = (
    training
    .groupby("Staff Number")
    .agg(
        courses_completed=("Requirement Title", "count"),
        unique_courses=("Requirement Title", "nunique"),
        training_cost=("Cost", "sum"),
        training_days=("Duration (days) this FY", "sum")
    )
    .reset_index()
)

# This is where I am jooining the features I created into the capability report using the staff number. The 
#how=left makes sure that even if someone is not in my training data, they they will still be included which
#is important because all people need to be represented, not just those that have had training. 
df = capability.merge(
    training_features,
    on="Staff Number",
    how="left"
)

# This is where I am printing out the dataframe, which is important to check a sample of the data to make 
#sure that the transformation and cleaning process has been performed as needed. This was used to diagnose
#the issues with the staff number as when it was originally run, it showed all of the new features I 
#created were blank. This I have kept in even after the diagnoses to ensure that I could pick up any
#errors that could have been caused as the python code was developed. I have commented this all out
#as after I used it to diagnose the issues I was having I no longer needed the outputs, but left this in
#just in case I was going to use it again in future, or wanted a quick reminder on the table and contents. 
print(df[
    [
        "Staff Number",
         "courses_completed",
       "unique_courses",
        "training_cost",
        "training_days",
        "ROLE"
    ]
].head(20))

# Here I am filling in any missing values with a 0, this is precaution to make sure that blanks in the
#dataset do not caause any issues. 
for col in [
    "courses_completed",
    "unique_courses",
    "training_cost",
    "training_days"
]:
    df[col] = df[col].fillna(0)

df["ROLE"] = df["ROLE"].fillna("Unknown")

#Here I am defining the target variable. This looks in the status column for the number 0 which
#will indicate someone still in the agency, or if not if will be a one which shows that they left

df["left_company"] = (
    df["Status"] == 0
).astype(int)

#Here I am creating a list of the columns within the dataset that are not numerical. Machine learning models
#must have only numeric values, and so I need to identify which are not and mark them as columns I want to
#change in the one-hot encoding
categorical_cols = [
    "Capability",
    "Grade",
    "Sub Capability/Team",
    "ROLE"
]

#Here I am using one hot encoding on the columns above, changing each of the categories into columns, which is
#what get_dummies does. For example it will take each of the grades and make them a column of 0 or 1, if that
#was originally what they had in them. df_encoded is the new dataframe that this information will be stored in
df_encoded = pd.get_dummies(
    df,
    columns=categorical_cols,
    drop_first=True
)

#These are the fields that I am ommiting from the raw data, staff number isn't relevant to the output, Status and left_company would 
#give the algoithms the answer as it says if they are here or not. FTE End Date isn't needed as I don't currently have the start 
#date and so it currently isn't usable to work out tenure. I had planned to try and get this information added but
#unfortunatley wasn't able to and so this remained in the code to exclude the information that I wasn't able to use, this 
#was simpler than removing it from the cleaned data as it was already created to not use this information anyway. 
X = df_encoded.drop(columns=[
    "Staff Number",
    "Status",
    "left_company",
    "FTE End Date" # Excluded 
])

y = df_encoded["left_company"]

#Within this code section I am using repeatedStratifiedKFold for splitting the data into train and test data through cross-validation, 
#What it means is that I am splitting the data into 5 parts each with the same amount of leavers and non leavers
#this is to ensure each one is balanced. A random state of 42 is there so that this is repeatable. The repeats
#is the amount of times the model is testes, so here I am doing it 25 times (5 times per split) which should
#in theory mean that the end results are repeatable and also reliable. 
cv = RepeatedStratifiedKFold(
    n_splits=5,
    n_repeats=5,
    random_state=42
)

#I have used Pipeline to scale the datea before passing into the the LR machine learing model. The pipeline
#is just the order that things get done here. The sclare is used so that single numerical values that are high
#like if I have training courses that cost 40k would not dominate the results and they provide a more balanced
#data set for the model. The Model part here is againt used to balance the leavers feature because the amount
#of people that stayed is way more than those that left. The iterations are used to basically say to the model
#that it can try up to 5000 times to improve the model, but it will stop at that point. The default value is 
#100 so this is giving my LR model more time to try and get it right. This made the outputs more reliable
#in testing as more doesn't always mean better, but it did here. 
lr_pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(max_iter=5000, class_weight="balanced"))
])

#Here I am creating the Random Forest Model which is the second algorithm that I wanted to use. The Estimators,
#500 branches will be used, a fixed random state is chosen so that my experimentation is repeatable and the class
#weighting is the same as the LR model I have spoken about above, due to the natural imbalance in the amount
#of peole that left compared to those that stayed. 
rf_cv_model = RandomForestClassifier(
    n_estimators=500,
    random_state=42,
    class_weight="balanced"
)

#Here I am doing the Gradient Boosting Model, the only hyper perameter I have changed here is the random state
#which I have done just to ensure this experiment is repeatable. I am not able to use class_weight as it
#does not exist within this model as something that can be changed. 
gb_cv_model = GradientBoostingClassifier(
    random_state=42
)

#Within this section I am using the models just above this code block and testing each of the machine learning
#models using cross validation. Each time the models are tested the ROC AUC is tested each time. It is storing
#the auc-roc performance metric for each of the 25 tests being completed on them and then giving me the mean
#which means that this is more reliable than running each algorithm once, and then using that as the auc-roc
#metric. 
lr_scores = cross_val_score(
    lr_pipeline,
    X,
    y,
    cv=cv,
    scoring="roc_auc",
    n_jobs=-1
)

rf_scores = cross_val_score(
    rf_cv_model,
    X,
    y,
    cv=cv,
    scoring="roc_auc",
    n_jobs=-1
)

gb_scores = cross_val_score(
    gb_cv_model,
    X,
    y,
    cv=cv,
    scoring="roc_auc",
    n_jobs=-1
)

#Within this Code block I am creating a table that I can use to show the results from the previous section.
#The Mean ROC_AUC shows the average, and the std ROC_AUC shows the standard deviation. 
cv_results = pd.DataFrame({
    "Model": [
        "Logistic Regression",
        "Random Forest",
        "Gradient Boosting"
    ],
    "Mean ROC_AUC": [
        lr_scores.mean(),
        rf_scores.mean(),
        gb_scores.mean()
    ],
    "Std ROC_AUC": [
        lr_scores.std(),
        rf_scores.std(),
        gb_scores.std()
    ]
})


print("Cross-validation ROC_AUC results:")
print(cv_results)

#Within this seciton I am comparing the scores from above, two at a time. This T-test section is looking at
#whether how each performed within each "fold" in comparison to each other. The t value that gets returned 
#will tell me if there was a difference how large it was, and the p value is whether or not there is a 
#serious statistical differnce in the outputs
lr_vs_rf = ttest_rel(lr_scores, rf_scores)
lr_vs_gb = ttest_rel(lr_scores, gb_scores)
rf_vs_gb = ttest_rel(rf_scores, gb_scores)

ttest_results = pd.DataFrame({
    "Comparison": [
        "Logistic Regression vs Random Forest",
        "Logistic Regression vs Gradient Boosting",
        "Random Forest vs Gradient Boosting"
    ],
    "t_statistic": [
        lr_vs_rf.statistic,
        lr_vs_gb.statistic,
        rf_vs_gb.statistic
    ],
    "p_value": [
        lr_vs_rf.pvalue,
        lr_vs_gb.pvalue,
        rf_vs_gb.pvalue
    ]
})

print("\nPaired t-test results:")
print(ttest_results)


#Within this section I am splitting the data as a whole into a 70/30 split for train and test. This will
#be used to evaluate the models and create a classification summary as well as a confusion matrix and this
#is needed to more accuratley predict how the models will perform in a realistic scenario. Again the 
#random state has been chosen to ensure repeatability
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.3,
    random_state=42,
    stratify=y
)

#Again this scaling is the same I used above in the cross validation section, to ensure that where numbers
#are used one doesn't dominate like training costs which are high, compared to how many people are in a role
#which is almost always a single digit value. 
scaler = StandardScaler()

X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)


# This is where I am training the LR model using the above train/test split, class weight is same as before
#the predict_proba section is going to produce % probablility of a leaver (e.g. 0.12). The coefficients
#are being used to allow me to assess which features are most associated with the probabiliyt of someone leaving.
lr = LogisticRegression(max_iter=5000, class_weight="balanced")

lr.fit(X_train_scaled, y_train)

lr_pred = lr.predict(X_test_scaled)
lr_prob = lr.predict_proba(X_test_scaled)[:, 1]


lr_coefficients = pd.DataFrame({
    "Feature": X.columns,
    "Coefficient": lr.coef_[0]
})

lr_coefficients = lr_coefficients.sort_values(
    by="Coefficient",
    ascending=False
)

# This is where I am training both Random Forest and Gradient boosting, using the train/test split, all 
#hyperperameters are the same as before for the same reasons. 
rf = RandomForestClassifier(
    n_estimators=500,
    random_state=42,
    class_weight="balanced"
)

rf.fit(X_train, y_train)

rf_pred = rf.predict(X_test)
rf_prob = rf.predict_proba(X_test)[:, 1]

# Gradient Boosting
gb = GradientBoostingClassifier(
    random_state=42
)

gb.fit(X_train, y_train)

gb_pred = gb.predict(X_test)
gb_prob = gb.predict_proba(X_test)[:, 1]

#Here I am priting a classification report that will show precision, recall, F1 score and support to help
#me to evaluate the models performance. This will give a clearer insight into how each of them is performing
#and is a cornerstone of my analysis of their performance and whether or not the outputs can be considered
#reliable. 
from sklearn.metrics import classification_report

print("Logistic Regression")
print(classification_report(y_test, lr_pred))

print("Random Forest")
print(classification_report(y_test, rf_pred))

print("Gradient Boosting")
print(classification_report(y_test, gb_pred))

#Here I am producting a confusion matrix, for each of the machine learnin gmodels to see how again how
#accuratley true positives are being as a further indication of how each of the models are performing. 

lr_cm = confusion_matrix(y_test, lr_pred)
rf_cm = confusion_matrix(y_test, rf_pred)
gb_cm = confusion_matrix(y_test, gb_pred)

print("Logistic Regression Confusion Matrix:")
print(lr_cm)

print("\nRandom Forest Confusion Matrix:")
print(rf_cm)

print("\nGradient Boosting Confusion Matrix:")
print(gb_cm)


#Table showing training cost, training days, courses completed and unique courses for staff. This is a handy
#Table that I am using to see if these have any material difference from the data, which could help to
#Indicate whether training has a positive or negative indluence. 
print(
    df.groupby("left_company")[
        ["training_cost", "training_days", "courses_completed", "unique_courses"]
    ].mean()
)

#This is where I am processing the results for the ROC_AUC
results = pd.DataFrame({
    "Model": [
        "Logistic Regression",
        "Random Forest",
        "Gradient Boosting"
    ],
    "Accuracy": [
        accuracy_score(y_test, lr_pred),
        accuracy_score(y_test, rf_pred),
        accuracy_score(y_test, gb_pred)
    ],
    "ROC_AUC": [
        roc_auc_score(y_test, lr_prob),
        roc_auc_score(y_test, rf_prob),
        roc_auc_score(y_test, gb_prob)
    ]
})

print(results)

#Within this section I am wanting to see what the most positive and the most negative features are that have 
#an impact on the prediction of attrition. I have chosen to only show 10 as that is more than enough for the
#requirements of the report I do this for each of the models

print(lr_coefficients.head(10))   # Most positive
print(lr_coefficients.tail(10))   # Most negative


# Gradient Boosting
gb_importance = pd.DataFrame({
    "Feature": X.columns,
    "Importance": gb.feature_importances_
})

gb_importance = gb_importance.sort_values(
    by="Importance",
    ascending=False
)

print(gb_importance.head(20))


#Random Forest
importance = pd.DataFrame({
    "Feature": X.columns,
    "Importance": rf.feature_importances_
})

importance = importance.sort_values(
    by="Importance",
    ascending=False
)

print(importance.head(20))
   Staff Number  courses_completed  unique_courses  training_cost  \
0          1435                1.0             1.0       0.000000   
1          7789                8.0             5.0    6463.160000   
2         10039                0.0             0.0       0.000000   
3         10156                2.0             2.0     776.000000   
4         10556                2.0             2.0       0.000000   
5         22897                NaN             NaN            NaN   
6         22926                7.0             5.0    3380.420000   
7         37528                1.0             1.0       0.000000   
8         39146                8.0             7.0   11534.350000   
9         45011                0.0             0.0       0.000000   
10        45417                2.0             2.0    1113.990000   
11        47006                NaN             NaN            NaN   
12        48123                1.0             1.0       0.000000   
13        48514                2.0             2.0     192.200000   
14        48668                3.0             3.0     530.160000   
15        61715               12.0            10.0    2083.160000   
16        62379                0.0             0.0       0.000000   
17        63273                3.0             3.0    2319.980000   
18        71700               22.0            19.0   23342.824719   
19        72479                NaN             NaN            NaN   

    training_days                                         ROLE  
0             2.0  Head of Infrastructure Operation & Services  
1            13.0    Junior Infrastructure Engineer - Telecoms  
2             1.0                         Practice Coordinator  
3             5.0              Senior Business Support Manager  
4             3.0                     Lead Technical Architect  
5             NaN                         Junior Test Engineer  
6            18.0                      Senior Business Analyst  
7             2.0                      Service Desk Specialist  
8            76.0                Senior Agile Delivery Manager  
9             1.0                             Delivery Support  
10            5.0                                Test Engineer  
11            NaN                      Project Support Officer  
12            1.0                      Service Desk Specialist  
13            3.0               Stakeholder Engagement Manager  
14            3.0                              Project Manager  
15           12.0           Infrastructure Engineer - Telecoms  
16            1.0                         Junior Test Engineer  
17           10.0                           IT Service Manager  
18           54.0                       Agile Delivery Manager  
19            NaN                         Junior Test Engineer  
Cross-validation ROC_AUC results:
                 Model  Mean ROC_AUC  Std ROC_AUC
0  Logistic Regression      0.593591     0.086954
1        Random Forest      0.694553     0.065515
2    Gradient Boosting      0.670900     0.076829

Paired t-test results:
                                 Comparison  t_statistic       p_value
0      Logistic Regression vs Random Forest    -9.015437  3.574063e-09
1  Logistic Regression vs Gradient Boosting    -5.965980  3.704825e-06
2        Random Forest vs Gradient Boosting     1.760148  9.112377e-02
Logistic Regression
              precision    recall  f1-score   support

           0       0.94      0.89      0.92       237
           1       0.14      0.24      0.17        17

    accuracy                           0.85       254
   macro avg       0.54      0.56      0.55       254
weighted avg       0.89      0.85      0.87       254

Random Forest
              precision    recall  f1-score   support

           0       0.94      0.99      0.96       237
           1       0.25      0.06      0.10        17

    accuracy                           0.93       254
   macro avg       0.59      0.52      0.53       254
weighted avg       0.89      0.93      0.90       254

Gradient Boosting
              precision    recall  f1-score   support

           0       0.94      0.99      0.96       237
           1       0.33      0.06      0.10        17

    accuracy                           0.93       254
   macro avg       0.63      0.53      0.53       254
weighted avg       0.90      0.93      0.91       254

Logistic Regression Confusion Matrix:
[[212  25]
 [ 13   4]]

Random Forest Confusion Matrix:
[[234   3]
 [ 16   1]]

Gradient Boosting Confusion Matrix:
[[235   2]
 [ 16   1]]
              training_cost  training_days  courses_completed  unique_courses
left_company                                                                 
0               4987.692512      16.934772           5.026650        4.598985
1               3370.718170      12.298246           4.350877        4.000000
                 Model  Accuracy   ROC_AUC
0  Logistic Regression  0.850394  0.576322
1        Random Forest  0.925197  0.564780
2    Gradient Boosting  0.929134  0.646438
                                               Feature  Coefficient
276                 ROLE_Senior Agile Delivery Manager     0.766455
144                 ROLE_Code - DIGITAL Academy - SDET     0.742767
50                Sub Capability/Team_Evolve Programme     0.728559
200                       ROLE_Junior Business Analyst     0.684869
16                        Sub Capability/Team_AC Squad     0.683456
143                ROLE_Code - DIGITAL Academy - CLOUD     0.625818
4                                        training_days     0.618913
167       ROLE_Digital Service Support Team Specialist     0.607633
270                       ROLE_Project Support Officer     0.593310
97   Sub Capability/Team_Service Improvement Programme     0.535285
                                               Feature  Coefficient
87       Sub Capability/Team_Project Management Office    -0.405895
157              ROLE_Communication Engagement Manager    -0.434282
2                                       unique_courses    -0.437546
46   Sub Capability/Team_Employee Experience Programme    -0.451560
337                                 ROLE_Test Engineer    -0.502914
241               ROLE_Legacy & SaaS Software Engineer    -0.629181
330                             ROLE_Software Engineer    -0.665820
269                               ROLE_Project Manager    -0.692737
0                                             Promoted    -1.414045
3                                        training_cost    -1.423517
                                               Feature  Importance
144                 ROLE_Code - DIGITAL Academy - SDET    0.078564
143                ROLE_Code - DIGITAL Academy - CLOUD    0.078172
338                                       ROLE_Unknown    0.074384
343      ROLE_Year in Industry - YR 6 Computer Science    0.072237
169          ROLE_Digital service Support Team Analyst    0.072131
4                                        training_days    0.062137
50                Sub Capability/Team_Evolve Programme    0.058869
142      ROLE_Code - APPRENTICESHIP - Business Analyst    0.038758
264               ROLE_Principal Solution Architect SI    0.038747
67   Sub Capability/Team_ITC - Workforce Management...    0.038674
200                       ROLE_Junior Business Analyst    0.037654
66   Sub Capability/Team_ITC - Capability and Talen...    0.037644
167       ROLE_Digital Service Support Team Specialist    0.033654
285                   ROLE_Senior Data Engineer - Mibi    0.033491
292         ROLE_Senior Infrastructure Engineer - EMAS    0.033483
1                                    courses_completed    0.030664
74   Sub Capability/Team_Legislative compliance Pro...    0.023932
3                                        training_cost    0.020335
13                                           Grade_HEO    0.019895
76                  Sub Capability/Team_Meatball Squad    0.019077
                                               Feature  Importance
3                                        training_cost    0.108971
4                                        training_days    0.108283
1                                    courses_completed    0.078597
2                                       unique_courses    0.071695
12                                            Grade_EO    0.023283
15                                           Grade_SEO    0.021731
8                                      Capability_ITSC    0.020561
7                                     Capability_I&OPS    0.019145
270                       ROLE_Project Support Officer    0.018690
13                                           Grade_HEO    0.018620
50                Sub Capability/Team_Evolve Programme    0.017835
11                                            Grade_AO    0.017067
10                                             Grade_7    0.013708
9                                       Capability_PPM    0.013446
143                ROLE_Code - DIGITAL Academy - CLOUD    0.011495
338                                       ROLE_Unknown    0.011475
144                 ROLE_Code - DIGITAL Academy - SDET    0.011112
308        ROLE_Senior Legacy & SaaS Software Engineer    0.010803
6                                      Capability_DSCC    0.010177
70   Sub Capability/Team_ITSM Practices and Transition    0.010087
In [2]:
#This section is all about SHAP and LIME and trying to get a better understanding of how the machine 
#learning algorithms have come to the conclusions that they have which is a key considerations when using
#machine learning in a HR context. 

#shap.TreeExplainer is a part of SHAP that is used to help explain machine learning algorithms that have 
#a decision tree based model and as I have used Random Forest and Gradient Boosting it should work well for
#helping explain how 2 out of my 3 models came to their conslusions.  The "explainers" in the last 2 lines
#of code 
rf_explainer = shap.TreeExplainer(rf)
rf_explanation = rf_explainer(X_test)
rf_shap_class1 = rf_explanation.values[:, :, 1]
rf_base_value = rf_explanation.base_values[:, 1]


# This is where I am using the data gathered from the above code block and making some visual charts that will
#help explain. This one will show which features are overall the most important. I have also had to include
#a section here that creates an image file as within my jupyter notebook the results were compressed, this also
#is done to help assist with using these artefacts within the project documentation as well. the bbox_inches
#element is there to trim whitespace that was around the charts to make them easier to use. 
shap.summary_plot(
    rf_shap_class1,
    X_test,
    plot_type="bar",
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Random Forest Bar Chart Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()
    
# This is almost identifical to the above, but it produces a beeswarm chart instead of a barchart. This chart
#gives a lot more detail as I can see everyone that went into the model, and the influence on staying or leaving
#on a per attribute level. The further to the left the more that attribute influenced them staying and the 
#further to the right the more it influenced them leaving. Blue means the value was low, red means high. 
shap.summary_plot(
    rf_shap_class1,
    X_test,
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Random Forest Beeswarm Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()
In [3]:
#This is SHAP for Gradient boosting, all the same code as before but specifically now
#for the gradient boosting algorithm 

gb_explainer = shap.TreeExplainer(gb)
gb_explanation = gb_explainer(X_test)
gb_shap_class1 = gb_explanation.values
gb_base_value = gb_explanation.base_values

shap.summary_plot(
    gb_shap_class1,
    X_test,
    plot_type="bar",
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Gradient Boosting Bar Chart Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()

shap.summary_plot(
    gb_shap_class1,
    X_test,
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Gradient Boosting Beeswarm Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()
In [6]:
#Here I want to use SHAP to analyse my Logisital Regression algorithm, this needs a different
#element from SHAP so I used lr_explanation insteaf of tree explainer to allow it to work with the differnet
#way that Logitical regression works. 


X_train_scaled_df = pd.DataFrame(
    X_train_scaled,
    columns=X.columns,
    index=X_train.index
)

X_test_scaled_df = pd.DataFrame(
    X_test_scaled,
    columns=X.columns,
    index=X_test.index
)

# Create the Logistic Regression SHAP explainer.
# This uses the scaled training data as the background/reference dataset.
lr_explainer = shap.Explainer(
    lr,
    X_train_scaled_df
)

# Generate SHAP values for the scaled test data.
lr_explanation = lr_explainer(
    X_test_scaled_df
)


shap.summary_plot(
    lr_explanation,
    X_test_scaled_df,
    plot_type="bar",
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Logistic Regression Bar Chart Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()


shap.summary_plot(
    lr_explanation,
    X_test_scaled_df,
    max_display=20,
    show=False
)

plt.savefig(
    "SHAP Logistic Regression Beeswarm Attrition.png",
    bbox_inches="tight",
    dpi=300
)

plt.close()
Background dataset has 591 samples but max_samples=100. Subsampling to 100 samples for SHAP value computation. To use all samples, set max_samples=591 when initializing the masker.
In [ ]: